/* nixonFTP
 * FTP client version 0.1
 * Copyright (C) 2010 NIXON Development Corporation.
 * All rights reserved.
 * http://members.shaw.ca/nixon.com
 */
package nixonftp;

import nixonftp.list.NXTable;
import nixonftp.ui.*;
import nixonftp.list.*;
import java.awt.*;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.UIManager.*;
import javax.swing.*;
import javax.swing.event.MenuEvent;
import javax.swing.event.PopupMenuEvent;
import sun.net.ftp.FtpLoginException;
import sun.net.ftp.FtpProtocolException;
import javax.swing.event.MenuListener;
import javax.swing.event.PopupMenuListener;
import nixonftp.error.NXLoginException;
import nixonftp.error.NXProtocolException;
import nixonftp.list.NXDefaultListModel;
import nixonftp.ui.NXLogPanel;
import nixonftp.ui.NXMenu;


public class NixonFTP extends java.applet.Applet implements ActionListener, DragGestureListener, PopupMenuListener {

	static NXSocketClient ftpClient;
	static JFrame frame;
	static NXSubFrame previewFrame;
	static NXLog ftpLog;
	private NXDetailsDialog mfrDetails;
	private NXToolbar toolBar;
	private NXFtpBrowserPanel remotePanel;
	private NXFtpBrowserPanel localPanel;
	private JButton btnLog;

	private NXContextMenu contextMenu;
	private ListTraverseMenuListener traverseMenuListener;
	private NXListAdapter menuList;
	private CrumbListener crumbActionListener;
	private ListPopupListener popupListener;

	private FileProgressWatcher progressUpdate = new FileProgressWatcher();
	private JProgressBar progressBar;
	private NXSplitPane splitBrowser;
	private NXSplitPane splitOverall;
	private NXSidebar sideBar;
	private javax.swing.Timer splitBrowserAnimator;
	private boolean splitBrowserReverse;
	private NXQueue queue;
	private JProgressBar overallProgressBar;
	private JPanel bottomPanel;
	private NXObjectIndex drives;
	private NXDragFeedbackLabel dragFeedbackLabel;
	private int proxyType = -1;
	private int PROXY_TYPE_HTTP = 0;
	private int PROXY_TYPE_SOCKS = 1;
	private NXQueuePanel queuePanel;
	private NXLogPanel logPanel;
	private JPopupMenu mmuMenu;
	private JMenuItem mmiFilters;
	private JCheckBoxMenuItem mmiLocal;
	private JCheckBoxMenuItem mmiSidebar;
	private NXListAdapter dragItem;
	private Boolean dragWait;
	String tempDir = System.getProperty("java.io.tmpdir");

	private LocalAction localAction;
	private LocalAction sidebarAction;
	private LocalAction filtersAction;
	
	private boolean isPreload = true;
	private String sgInitialDir = "";

	public NixonFTP() {
		frame = new JFrame("nixonFTP");
		try {
			frame.setIconImages(NXIcon.iconList);
		} catch (NoSuchMethodError ex) {
			frame.setIconImage((Image) NXIcon.iconList.get(0));
		}

		frame.getRootPane().putClientProperty("apple.awt.brushMetalLook", Boolean.TRUE);
		Container contentPane = frame.getContentPane();

		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setPreferredSize(new Dimension(816, 580));
		frame.setGlassPane(new NXDragFeedbackLabel());
		construct(contentPane);
		frame.pack();
		frame.setVisible(true);
		
		String end = tempDir.substring(tempDir.length());
		if (end.equals("/") || end.equals("\\")) {
			tempDir = tempDir.substring(0, tempDir.length() - 1);
		}
	}

	private void construct(Container contentPane) {
		ftpLog = new NXLog();

		toolBar = new NXToolbar(this);
		toolBar.setOrientation(JToolBar.VERTICAL);
		localAction = new LocalAction("Local", null, "Show Local Pane", new Integer(KeyEvent.VK_L), KeyStroke.getKeyStroke("control L"));
		toolBar.btnLocal.setAction(localAction);

		sidebarAction = new LocalAction("Sidebar", null, "Show Sidebar", new Integer(KeyEvent.VK_S), KeyStroke.getKeyStroke("control K"));
		toolBar.btnQueue.setAction(sidebarAction);

		filtersAction = new LocalAction("Filters", NXIcon.filters, "Filters", new Integer(KeyEvent.VK_F), KeyStroke.getKeyStroke("control D"));
		SpringLayout frameLayout = new SpringLayout();
		contentPane.setLayout(frameLayout);


		remotePanel = new NXFtpBrowserPanel(true, false);
		//NXScrollPane remoteScroll = new NXScrollPane(remotePanel);
		remotePanel.btnConnect.addActionListener(this);
		remotePanel.txtHost.addActionListener(this);
		remotePanel.txtUser.addActionListener(this);
		remotePanel.txtPassword.addActionListener(this);
		remotePanel.txtInitialDir.addActionListener(this);
		//addRemoteList("", 0);

		localPanel = new NXFtpBrowserPanel(false, true);
		//NXScrollPane localScroll = new NXScrollPane(localPanel);
		splitBrowser = new NXSplitPane(JSplitPane.VERTICAL_SPLIT,
				remotePanel, null);
		splitBrowser.setSecondComponent(localPanel);

		sideBar = new NXSidebar();
		splitOverall = new NXSplitPane(JSplitPane.HORIZONTAL_SPLIT,
				null, splitBrowser);
		splitOverall.setFirstComponent(sideBar);

		bottomPanel = new JPanel();
		bottomPanel.setLayout(new GridLayout(0, 2));
		bottomPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY));

		progressBar = new JProgressBar(0, 100);
		progressBar.setVisible(false);
		progressBar.setStringPainted(true);
		bottomPanel.add(progressBar);

		overallProgressBar = new JProgressBar(0, 100);
		overallProgressBar.setVisible(false);
		overallProgressBar.setStringPainted(true);
		overallProgressBar.setString("All");
		bottomPanel.add(overallProgressBar);

		
		contentPane.add(splitOverall);
		contentPane.add(toolBar);
		contentPane.add(bottomPanel);

		// Make object expand to window.
		frameLayout.putConstraint(SpringLayout.NORTH, splitOverall, 0,
				SpringLayout.NORTH, contentPane);
		frameLayout.putConstraint(SpringLayout.NORTH, toolBar, 0,
				SpringLayout.NORTH, contentPane);
		frameLayout.putConstraint(SpringLayout.SOUTH, toolBar, 0,
				SpringLayout.SOUTH, contentPane);

		frameLayout.putConstraint(SpringLayout.EAST, toolBar, 0,
				SpringLayout.EAST, contentPane);

		/*frameLayout.putConstraint(SpringLayout.EAST, splitOverall, 0,
				SpringLayout.EAST, contentPane);*/
		frameLayout.putConstraint(SpringLayout.WEST, splitOverall, 0,
				SpringLayout.WEST, contentPane);
		frameLayout.putConstraint(SpringLayout.SOUTH, splitOverall, 0,
				SpringLayout.NORTH, bottomPanel);
		frameLayout.putConstraint(SpringLayout.SOUTH, bottomPanel, 0,
				SpringLayout.SOUTH, contentPane);
		frameLayout.putConstraint(SpringLayout.EAST, bottomPanel, 0,
				SpringLayout.EAST, contentPane);
		frameLayout.putConstraint(SpringLayout.WEST, bottomPanel, 0,
				SpringLayout.WEST, contentPane);
		frameLayout.putConstraint(SpringLayout.EAST, splitOverall, 0,
				SpringLayout.WEST, toolBar);

		queue = new NXQueue();

		addLocalList("", 0);
		//if (File.listRoots()[0].getPath().equals("/")) {
		//	(new ListingWorker("/", true)).execute();
		//} else {
		(new DriveWorker()).execute();
		//}
		splitBrowser.setDividerLocation(0.6);
		doTable(remotePanel.table);
		doTable(localPanel.table);

		queuePanel = new NXQueuePanel();
		sideBar.addImpl("Queue", queuePanel);

		queuePanel.btnPauseAll.addActionListener(this);
		queuePanel.btnStop.addActionListener(this);
		queuePanel.btnStopAll.addActionListener(this);

		logPanel = new NXLogPanel();
		ftpLog.setArea(logPanel);
		sideBar.addImpl("Log", logPanel);

		contextMenu = new NXContextMenu();
		traverseMenuListener = new ListTraverseMenuListener();
		//transferToActionListener = new TransferToActionListener();
		contextMenu.transferTo.addMenuListener(traverseMenuListener);
		Object[] menuItems = (Object[]) contextMenu.getIndex().values().toArray();
		popupListener = new ListPopupListener();
		for (int x = 0; x < menuItems.length; x++) {
			try {
				((JMenuItem) menuItems[x]).addActionListener(popupListener);
			} catch (Exception e) {
				continue;
			} //separator
		}

		KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
		  kfm.addKeyEventDispatcher( new KeyEventDispatcher() {

			 @Override
			 public boolean dispatchKeyEvent(KeyEvent e) {
				KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
				if ( actionMap.containsKey(keyStroke) ) {
				  final Action a = actionMap.get(keyStroke);
				  final ActionEvent ae = new ActionEvent(e.getSource(), e.getID(), null );
				  SwingUtilities.invokeLater( new Runnable() {
					 @Override
					 public void run() {
						a.actionPerformed(ae);
					 }
				  } );
				  return true;
				}
				return false;
			 }
		  });

		actionMap.put(KeyStroke.getKeyStroke("control L"), localAction);
		actionMap.put(KeyStroke.getKeyStroke("control K"), sidebarAction);
		actionMap.put(KeyStroke.getKeyStroke("control D"), filtersAction);
		makeMenu();
	}

	private HashMap<KeyStroke, Action> actionMap = new HashMap<KeyStroke, Action>();

	private void makeMenu() {
		//JMenuBar bar = new JMenuBar();
		mmuMenu= new JPopupMenu();
		
		mmiFilters = new JMenuItem("Filters...");
		mmiFilters.setIcon(NXIcon.filtersSmall);
		mmuMenu.add(mmiFilters);
		mmiFilters.addActionListener(this);
		mmiFilters.setMnemonic('F');
		mmiFilters.setAccelerator(KeyStroke.getKeyStroke("control D"));

		mmiLocal = new JCheckBoxMenuItem(localAction);
		mmuMenu.add(mmiLocal);

		mmiSidebar = new JCheckBoxMenuItem(sidebarAction);
		mmuMenu.add(mmiSidebar);
		//mmiSidebar.addActionListener(this);

		mmuMenu.addPopupMenuListener(this);
		//mmuMenu.addMenuListener(this);
	}

	public NixonFTP(Container contentPane, NXDragFeedbackLabel lbl) {
		construct(contentPane);
		dragFeedbackLabel = lbl;
	}

	private void toggleSidebar() {
		boolean b = splitOverall.toggle(true);
		sideBar.setVisible(b);
		mmiSidebar.setSelected(b);
		toolBar.btnQueue.setSelected(b);
	}

	class LocalAction extends AbstractAction {
		public LocalAction(String text, ImageIcon icon,
							 String desc, Integer mnemonic, KeyStroke accel) {
			super(text, icon);
			putValue(NAME, text);
			putValue(SHORT_DESCRIPTION, desc);
			putValue(MNEMONIC_KEY, mnemonic);
			putValue(ACCELERATOR_KEY, accel);
		}
		public void actionPerformed(ActionEvent e) {
			String c = (String) getValue(NAME);
			if (c == null || c.equals("Local")) {
				toggleLocal();
			} else if (c.equals("Sidebar")) {
				toggleSidebar();
			} else if (c.equals("Filters")) {
				toolBar.btnFilters.setVisible(true);
				doFilters();
			}
		}
	}

	public void setProxyType(String type) {
		if (type == null) return;
		if (type.equals("0")) {
			proxyType = PROXY_TYPE_HTTP;
		} else if (type.equals("1")) {
			proxyType = PROXY_TYPE_SOCKS;
		}
	}

	private void toggleLocal() {
		boolean b = splitBrowser.toggle(false);
		toolBar.btnLocal.setSelected(b);
		mmiLocal.setSelected(b);
	}

	public void actionPerformed(ActionEvent e) {
		Object source = e.getSource();
		boolean b;
		boolean accountPanel = source.equals(remotePanel.btnConnect)
				|| source.equals(remotePanel.txtHost)
				|| source.equals(remotePanel.txtUser)
				|| source.equals(remotePanel.txtPassword)
				|| source.equals(remotePanel.txtInitialDir);
		if (accountPanel) { //from account interface inside browser
			String[] s = remotePanel.getValues();
			connect(s[0], s[1], s[2], remotePanel.radSftp.isSelected());
		} else if (source.equals(toolBar.btnConnect)) {
			if (!remotePanel.init) {
				switchAccountPanel();
			}
		} else if (source.equals(toolBar.btnListing)) {
			if (!remotePanel.accountPanel.isVisible()) {
				doListing(false, !remotePanel.listing);
			}
		} else if (source.equals(toolBar.btnListingLocal)) {
			doListing(true, !localPanel.listing);
		} else if (source.equals(mmiFilters)) {
			toolBar.btnFilters.setVisible(true);
			doFilters();
		} else if (source.equals(toolBar.btnFilters)) {
			doFilters();
		} else if (source.equals(toolBar.btnLocal) || source.equals(mmiLocal)) {
			toggleLocal();
		} else if (source.equals(toolBar.btnMenu)) {
			mmuMenu.show(toolBar.btnMenu, 0, toolBar.btnMenu.getHeight());
		} else if (source.equals(toolBar.btnQueue) || source.equals(mmiSidebar)) {
			//queuePanel.setVisible(toolBar.btnQueue.isSelected());
			toggleSidebar();
		} else if (source.equals(queuePanel.btnPauseAll)) {
			b = queuePanel.btnPauseAll.isSelected();
			queue.paused = b;
			if (queue.paused) {
				queue.timer.stop();
			}
			if (!queue.paused && !queue.current) queue.continueQueue();
		} else if (source.equals(queuePanel.btnStop)) {
			queue.stopCurrent();
		} else if (source.equals(queuePanel.btnStopAll)) {
			queue.stopCurrent();
			queue.removeAll();
		}
	}

	private void doListing(boolean local, boolean b) {
		NXFtpBrowserPanel panel = (local) ? localPanel : remotePanel;
		panel.listing = b;
		panel.listingPanel.setVisible(b);
		panel.browserScroll.setVisible(!b);

		ImageIcon i = null;

		if (b) {
			NXList list = panel.getListBoxes(-1);
			String dir = list.getDirectory();
			String getDir = (dir.equals("")) ? "/" : dir;
			if (local) {
				(new ListingWorker(dir, true)).execute();
			} else {
				updateList(panel.table, (NXObjectIndex) ftpClient.getObjectIndex().get(getDir), local);
			}
			doCrumbs(dir, local);
			panel.browserScroll.getViewport().setViewPosition(new Point(0, 0));
			i = NXIcon.columns;
		} else {
			traversePath(local, panel.table.getDirectory());
			i = NXIcon.detail;
		}

		if (!local) {
			/*JButton btn = (local) ? toolBar.btnListingLocal : toolBar.btnListing;
			btn.setIcon(i);*/
			toolBar.btnListing.setIcon(i);
		}
	}

	private void switchAccountPanel() {
		boolean b = remotePanel.toggleAccountPanel();
		if (b) {
			String[] s = ftpClient.getCredentials();
			remotePanel.txtHost.setText(s[0]);
			remotePanel.txtUser.setText(s[1]);
			remotePanel.txtPassword.setText(s[2]);
		}
		remotePanel.validateAccountPanel();
		//toolBar.btnConnect.setText((b) ? "Return" : "");
		if (!remotePanel.init) {
			toolBar.btnListing.setVisible(!b);
			//toolBar.btnFilters.setVisible(!b);
		}
	}

	private void traversePath(boolean local, String dir) {
		NXFtpBrowserPanel panel = (local) ? localPanel : remotePanel;
		String[] dirs = dir.split("/");
		panel.removeList(1);

		NXList list;
		String initialPath = "";
		String newDir;
		NXObjectIndex oi;
		String addDir;

		for (int x = 0; x < dirs.length; x++) {
			newDir = initialPath + "/" + dirs[x];
			addDir = (newDir.equals("/")) ? "" : newDir;

			if (x == 0 && local) {
				addLocalList("", 0);
				(new DriveWorker()).execute();
				continue;
			}
			
			if (local) {
				addLocalList(addDir, x);
			} else {
				addRemoteList(addDir, x);
			}

			list = panel.getListBoxes(-1);

			if (local) {
				(new ListingWorker(newDir, true, list)).execute();
			} else {
				oi = (NXObjectIndex) ftpClient.getObjectIndex().get(newDir);
				if (oi != null) {
					updateList(list, oi, false);
				}
			}

			initialPath = addDir;
		}
	}

	private Proxy getProxy() {
		if (proxyType == -1) {
			return null;
		}
		Proxy proxy = null;

		String proxyHost = System.getProperty("ftp.proxyHost");
		if (proxyHost != null) {
			System.out.println("Using proxy server");
			int proxyPort = 1080;
			String portProperty = System.getProperty("ftp.proxyPort");
			if (portProperty != null) {
				try {
					proxyPort = Integer.parseInt(portProperty);
				} catch (NumberFormatException e) {
				}
			}
			SocketAddress addr = new InetSocketAddress(proxyHost, proxyPort);
			Proxy.Type type = (proxyType == 0) ? Proxy.Type.HTTP : Proxy.Type.SOCKS;
			proxy = new Proxy(type, addr);
		}
		return proxy;
	}

	private void connect(String host, String user, String password, boolean secure) {
		if (ftpClient != null) {
			ftpClient.disconnect();
		}
		if (!secure) {
			ftpClient = new NXFtpClient(ftpLog, getProxy());
		} else {
			ftpClient = new NXSshFtpClient(ftpLog);
		}
		ftpClient.setCredentials(
				new String[]{host,
					user,
					password,
					""});

		if (host.isEmpty()) host = "localhost";
		if (frame != null) frame.setTitle(host + " - nixonFTP");
		remotePanel.setInit(false);
		isPreload = remotePanel.chkPreload.isSelected();
		switchAccountPanel();
		(new ConnectWorker()).execute();
	}

	private void updateList(NXListAdapter list, NXObjectIndex oi, boolean local) {
		Object model = null;
		Object[] data = null;
		NXDefaultListModel listModel = null;
		NXTableModel tableModel = null;
		NXList nixonList = null;
		NXTable table = null;
		NXFilter[] filters = null;
		boolean blFilter = false;
		NXFtpBrowserPanel panel = (local) ? localPanel : remotePanel;

		if (!panel.listing) {
			listModel = new NXDefaultListModel();
			nixonList = (NXList) list;
			filters = nixonList.getFilters();
			blFilter = filters != null;
		} else {
			tableModel = new NXTableModel();
			table = panel.table;
		}

		boolean showOnly = false;
		boolean hide = false;
		boolean add = false;

		for (Object obj : oi.objects.values()) {
			NXObjectIndex oioi = (NXObjectIndex) obj;
			if (blFilter) {
				add = false;
				hide = false;
				NXFilter[] filter = nixonList.filter(filters, oioi);
				for (int x = 0; x < filter.length; x++) {
					if (filter[x] != null) {
						hide = filter[x].action == NXFilter.HIDE;
						showOnly = filter[x].action == NXFilter.SHOW;
						add = filter[x].action == NXFilter.SHOW || filter[x].action == NXFilter.COLOR;
					} else {
						if (!showOnly) {
							add = true;
						}
					}
				}
				if (!add && hide) {
					continue;
				}
			}
			if (panel.listing) {
				tableModel.addRow(oioi);
			} else {
				listModel.addElement(oioi);
			}
		}

		if (!panel.listing) {
			model = new NXSortedListModel(listModel, NXSortedListModel.SortOrder.DESCENDING, new NXObjectComparator());
			nixonList.setModel((ListModel) model);
		} else {
			table.setModel(tableModel);
			table.getColumnModel().getColumn(0).setHeaderValue("Name");
			table.getColumnModel().getColumn(1).setHeaderValue("Size");
			table.getColumnModel().getColumn(2).setHeaderValue("Modified");
			table.dirty = false;
			table.invalidate();
		}
	}

	public void popupMenuWillBecomeVisible(PopupMenuEvent e) {

	}

	public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
		//throw new UnsupportedOperationException("Not supported yet.");
	}

	public void popupMenuCanceled(PopupMenuEvent e) {
		//throw new UnsupportedOperationException("Not supported yet.");
	}

	class NXObjectComparator implements Comparator {

		public int compare(Object o1, Object o2) {
			NXObjectIndex oi1 = (NXObjectIndex) o1;
			NXObjectIndex oi2 = (NXObjectIndex) o2;
			String name1 = oi1.name;
			String name2 = oi2.name;

			if (oi1.type == oi2.type) {
				return name2.compareToIgnoreCase(name1);
			} else if (oi1.type > oi2.type) { //folders/links take precedence
				return 1;
			} else {
				return -1;
			}
		}
	}

	class ConnectWorker extends SwingWorker {

		String[] credentials;

		public ConnectWorker() {
			super();
			credentials = ftpClient.getCredentials();
			setStatus("Connecting to \"" + credentials[0] + "\"...", true);
		}

		@Override
		protected Object doInBackground() throws Exception {
			NXMessageLabel lbl = new NXMessageLabel();
			String[] sError = new String[2];

			try {
				
				sgInitialDir = remotePanel.txtInitialDir.getText();
				
				if (sgInitialDir.isEmpty()) {
					sgInitialDir = "/";
				}
				ftpClient.connect(sgInitialDir);

			} catch (Exception ex) {
				if (ex instanceof FtpLoginException || ex instanceof NXLoginException) {
					sError[0] = "Could not log in to the server \"" + credentials[0] + "\".";
					sError[1] = "Make sure that you entered your username and password correctly.";
				} else if (ex instanceof FtpProtocolException || ex instanceof NXProtocolException) {
					sError[0] = "Could not connect to the server \"" + credentials[0] + "\".";
					sError[1] = "Make sure that the server is an RFC 959-compliant FTP server.";
				} else if (ex instanceof UnknownHostException) {
					sError[0] = "Could not find the server \"" + credentials[0] + "\".";
					sError[1] = "Make sure you entered the address correctly. The server could be down at the moment. ";
				} else if (ex instanceof SocketException) {
					sError[0] = "Could not get listing from the server \"" + credentials[0] + "\".";
					sError[1] = "If you have a firewall, it may be causing this problem. Try configuring your firewall or disabling it.";
				} else if (ex instanceof IOException) {
					sError[0] = "Could not connect to the server \"" + credentials[0] + "\".";
					sError[1] = "The error was: " + ex.getMessage();
				} else {
					sError[0] = "Could not connect to the server \"" + credentials[0] + "\".";
					sError[1] = "The error was: " + ex.getMessage();
				}
			}

			if (sError[0] != null) {
				lbl.setInfo(sError[0], sError[1]);
				JOptionPane.showMessageDialog(frame, lbl,
						"", JOptionPane.ERROR_MESSAGE);
				switchAccountPanel();
			}
			return new Object();
		}

		@Override
		public void done() {
			HashMap oi = ftpClient.getObjectIndex();
			NXObjectIndex dir = (NXObjectIndex) oi.get(sgInitialDir);
			if (dir != null) {
				NXListAdapter obj = null;
				if (!remotePanel.listing) {
					addRemoteList((sgInitialDir.equals("/") ? "" : sgInitialDir), 0);
					obj = remotePanel.getListBoxes(-1);
				} else {
					remotePanel.table.setDirectory("");
				}
				updateList(obj, dir, false);
				NXQueueItem newItem = new NXQueueItem("preload", "", sgInitialDir, "");
				newItem.preload = getDirectoriesFromHash(dir.objects);
				queue.add(newItem, false);
			}
			progressBar.setVisible(false);
			
			String welcome = ftpClient.getWelcome();
			parseWelcome(welcome);
		}
	};

	private void parseWelcome(String welcome) {
		if (frame == null) return;
		String host = ftpClient.getCredentials()[0].toLowerCase();
		if (welcome != null) {
			String[] lines = welcome.split("\n");
			String[] words; int y; String title; String word;

			String[] hostWords;
			if (host.contains(".")) {
				hostWords = host.split("\\.");
			} else {
				hostWords = new String[] { host };
			}

			for (int x = 0; x < lines.length; x++) {
				words = lines[x].split(" |\\.");
				for (y = 0; y < words.length; y++) {
					word = words[y].toLowerCase();
					if (checkHost(hostWords, word) && !word.equals("ftp") && !word.isEmpty()) {
						title = (lines[x].startsWith("220")) ? lines[x].substring(4) : lines[x];
						title = title.trim();
						frame.setTitle(title + " - nixonFTP");
						return;
					}
				}
			}
		} else {
			frame.setTitle(host + " - nixonFTP");
		}
	}

	private boolean checkHost(String[] hostWords, String word) {
		for (int x = 0; x < hostWords.length; x++) {
			if (hostWords[x].equals(word)) {
				return true;
			}
		}
		return false;
	}

	class DriveWorker extends SwingWorker {

		File[] roots;

		@Override
		protected Object doInBackground() throws Exception {
			roots = File.listRoots();
			return new Object();
		}

		@Override
		public void done() {
			JList list = null;
			NXDefaultListModel listModel = new NXDefaultListModel();
			NXTable table = null;
			NXTableModel tableModel = null;
			NXSortedListModel sortedModel = null;

			if (localPanel.listing) {
				table = localPanel.table;
				tableModel = new NXTableModel();
			} else {
				list = localPanel.getListBoxes(0);
			}

			String drivePath;
			drives = new NXObjectIndex('z', "");
			for (Object obj : roots) {
				File drive = (File) obj;
				drivePath = drive.getPath();
				if (drivePath.length() >= 3) {
					drivePath = drivePath.substring(0, 2);
				}
				NXObjectIndex driveIndex = new NXObjectIndex('z', drivePath);
				if (localPanel.listing) {
					tableModel.addRow(driveIndex);
				} else {
					listModel.addElement(driveIndex);
				}
				drives.objects.put(drivePath, driveIndex);
			}
			NXObjectIndex homeItem = new NXObjectIndex('h', "Home");
			if (localPanel.listing) {
				tableModel.addRow(homeItem);
				table.setModel(tableModel);
			} else {
				listModel.addElement(homeItem);
				sortedModel = new NXSortedListModel(listModel, NXSortedListModel.SortOrder.DESCENDING, new NXObjectComparator());
				list.setModel(sortedModel);
			}
			drives.objects.put("Home", homeItem);
		}
	};

	class ListingWorker extends SwingWorker {

		private String directory;
		private boolean local = false;
		private File[] files;
		private NXListAdapter list = null;
		private boolean blDrives = false;
		private int listingSize;
		private String fake;

		protected ListingWorker(String directory, boolean local, Object list) {
			this.local = local;
			if (list != null) {
				this.list = (NXListAdapter) list;
			}
			setUp(directory);
		}

		protected ListingWorker(String directory, boolean local) {
			this.local = true;
			setUp(directory);
		}

		protected ListingWorker(String directory) {
			setUp(directory);
			toolBar.btnListing.setEnabled(false);
		}

		public void setFakeDirectory(String dir) {
			fake = dir;
		}

		@Override
		protected Object doInBackground() throws Exception {
			if (local) {
				if (directory.equals("")) {
					blDrives = true;
				} else {
					if (directory.equals("/")) {
						directory = "";
					}
					File f = new File(directory + "/");
					files = f.listFiles(new HiddenFileFilter());
				}
			} else {
				listingSize = ftpClient.indexDirectory(directory);
			}
			return new Object();
		}

		private void doLocal() {
			String name;
			char type;
			char[] perms = new char[3];
			String strPerms;
			long size;
			Date date;
			boolean exe = false;

			NXObjectIndex root = new NXObjectIndex('d', directory, "rwxrwxrwx", "", "", 0, 0, new Date());

			File testFile = new File("");
			try {
				exe = testFile.canExecute();
				exe = true;
			} catch (NoSuchMethodError e) { //Java 5
				exe = false;
			}

			if (files != null) {
				for (int x = 0; x < files.length; x++) {
					name = files[x].getName();
					type = (files[x].isDirectory()) ? ('d') : ('-');
					perms[0] = (files[x].canRead()) ? ('w') : ('-');
					perms[1] = (files[x].canWrite()) ? ('r') : ('-');

					if (exe) { //Java 6
						perms[2] = (files[x].canExecute()) ? ('x') : ('-');
					} else {
						perms[2] = '-';
					}

					strPerms = new String(perms);
					size = files[x].length();
					date = new Date(files[x].lastModified());
					NXObjectIndex oi = new NXObjectIndex(type, name, strPerms, "", "", size, 0, date);
					root.objects.put(oi.name, oi);
				}
			}
			if (list == null) {
				list = localPanel.getListBoxes(-1);
			}
			updateList(list, root, true);
			resetBars();
		}

		private void resetBars() {
			if (queue.queue.isEmpty()) {
				progressBar.setVisible(false);
				overallProgressBar.setVisible(false);
			}
		}

		@Override
		public void done() {
			if (blDrives) {
				(new DriveWorker()).execute();
				resetBars();
			} else if (local) {
				doLocal();
			} else {
				progressBar.setVisible(false);
				toolBar.btnListing.setEnabled(true);

				NXObjectIndex dirObj = (NXObjectIndex) ftpClient.getObjectIndex().get(directory);
				if (fake != null) {
					ftpClient.fakeDirectory(dirObj, fake);
				}
				if (remotePanel.listing) {
					updateList(null, dirObj, false);
				} else {
					boolean listDefined = (list != null);
					if (!listDefined) {
						list = remotePanel.getListBoxes(-1);
					}
					if (list.getDirectory().equals(directory) || listDefined) { //user didn't move to another directory
						updateList(list, dirObj, false);
					}
				}
				queue.totalSize += listingSize;
				queue.totalTransferred += listingSize;
				preloadDirectory(directory, dirObj.objects);
				queue.process();
			}
		}

		private void setUp(String directory) {
			this.directory = directory;
			setStatus("Getting contents of \"" + directory + "\"...", true);
		}
	};

	private void preloadDirectory(String dir, HashMap objects) {
		//if (ftpClient.getObjectIndex().containsKey(dir)) return;
		if (!isPreload) return;
		NXQueueItem newItem = new NXQueueItem("preload", "", dir, "");
		newItem.preload = getDirectoriesFromHash(objects);
		queue.add(newItem, false);
	}

	private Object[] getDirectoriesFromHash(HashMap obj) {
		Object[] values = obj.values().toArray();
		NXObjectIndex oi;
		HashMap ret = new HashMap();
		for (int x = 0; x < values.length; x++) {
			oi = (NXObjectIndex) values[x];
			if (oi.type == 2 || oi.realType == 2) {
				ret.put(oi.name, oi);
			}
		}
		return ret.values().toArray();
	}

	private void uploadFromFileList(String dir, java.util.List files) {
		File file; String basePath;
		NXListAdapter list = remotePanel.getListIfShown(dir);
		File[] folderFiles; NXObjectIndex[] folderObj;

		for (int x = 0; x < files.size(); x++) {
			file = (File) files.get(x);
			if (file.isDirectory()) {
				basePath = dir + '/' + file.getName();
				//recursiveUpload(file.listFiles(), basePath);
				folderFiles = file.listFiles();
				folderObj = fileListToIndex(folderFiles);
				uploadFromIndex(basePath, file.getPath(), folderObj, null);
			} else {
				queue.add(new NXQueueItem("upload", file.getParent(), dir, file.getName(), list, file.length()), false);
			}
		}
	}

	private void recursiveUpload(File[] files, String basePath) {
		createDirectoryIf(basePath);
		NXListAdapter list = remotePanel.getListIfShown(basePath);
		for (int x = 0; x < files.length; x++) {
			if (files[x].isDirectory()) {
				String name = files[x].getPath();
				String newPath = basePath + '/' + NXFormat.getNameFromPath(name, false);
				recursiveUpload(files[x].listFiles(), newPath);
			} else {
				queue.add(new NXQueueItem("upload", files[x].getParent(), basePath, files[x].getName(), list, files[x].length()), false);
			}
		}
	}

	class NXListTransferHandler extends TransferHandler {

		DataFlavor localObjectFlavor;
		DataFlavor fileFlavor;
		String localArrayListType = DataFlavor.javaJVMLocalObjectMimeType;
		private final boolean local;
		private final boolean listing;

		public NXListTransferHandler(boolean local, boolean listing) {
			this.local = local;
			this.listing = listing;
			try {
				localObjectFlavor = new DataFlavor(localArrayListType);
				fileFlavor = DataFlavor.javaFileListFlavor;
			} catch (ClassNotFoundException ex) {
			}
		}

		@Override
		public int getSourceActions(JComponent c) {
			if (dragItem != null) {
				if (((NXListAdapter) c).getLocal() == dragItem.getLocal()) return MOVE;
				else return COPY;
			} else {
				return COPY_OR_MOVE;
			}
			//return NONE;
		}

		@Override
		public Transferable createTransferable(JComponent c) {
			NXObjectIndex[] objs = null;
			String dir = "";
			boolean blLocal = false;
			if (listing) {
				NXTable table = (NXTable) c;
				int[] rows = table.getSelectedRows();
				NXTableModel model = (NXTableModel) table.getModel();
				objs = new NXObjectIndex[rows.length];
				for (int x = 0; x < rows.length; x++) {
					objs[x] = model.getValueAt(rows[x]);
				}
				dir = table.getDirectory();
				blLocal = table.getLocal();
			} else {
				NXList list = (NXList) c;
				objs = list.getSelectedObjects();
				dir = list.getDirectory();
				blLocal = list.getLocal();
			}
			dragItem = (NXListAdapter) c;
			return new NXObjectTransferable(objs, dir, blLocal);
		}

		@Override
		public void exportDone(JComponent c, Transferable t, int action) {
			if (action == MOVE && local) {
				reloadList((NXListAdapter) c);
			}
		}

		@Override
		public boolean importData(JComponent comp, Transferable t) {
			NXObjectIndex[] oi = null;
			NXListAdapter list = (NXListAdapter) comp;

			DataFlavor unix = null;
			try {
				DataFlavor[] flavors = t.getTransferDataFlavors();

				try {
					unix = new DataFlavor("text/uri-list;class=java.io.Reader");
				} catch (ClassNotFoundException ex) {
					Logger.getLogger(NixonFTP.class.getName()).log(Level.SEVERE, null, ex);
				}

				String dir = list.getDirectory();
				NXObjectIndex val = list.getSelectedValue();

				if (val != null && (val.type == 2 || val.realType == 2) && list.dropLeft()) {
					dir += '/' + val.name;
				}

				for (int x = 0; x < flavors.length; x++) {
					if (flavors[x].isFlavorJavaFileListType()) {
						java.util.List fileList = (java.util.List) t.getTransferData(DataFlavor.javaFileListFlavor);
						uploadFromFileList(dir, fileList);
						break;
					} else if (flavors[x].equals(unix)) {
						uploadFromFileList(dir, doUnix(flavors[x], t));
					} else if (flavors[x].equals(localObjectFlavor)) {
						Object[] data = (Object[]) t.getTransferData(flavors[x]);
						NXObjectIndex[] o = (NXObjectIndex[]) data[0];
						String dir2 = (String) data[1];
						boolean importFromLocal = data[2].equals(true);

						if (dir2.equals(dir)) return false;

						if (importFromLocal == list.getLocal()) { //move operation
							if (importFromLocal) { localMoveFromIndex(dir2, dir, o);
							} else { remoteMoveFromIndex(dir2, dir, o); }
						} else if (importFromLocal) {
							uploadFromIndex(dir, dir2, o, list);
						} else {
							downloadFromIndex(dir2, dir, o, list);
							return true;
						}
						break;
					}
				}

			} catch (UnsupportedFlavorException ex) {
			} catch (IOException ex) {
			}
			return true;
		}

		private ArrayList doUnix(DataFlavor f, Transferable t) throws UnsupportedFlavorException, IOException {
			BufferedReader read = new BufferedReader(f.getReaderForText(t));
			// Delete "file://" protocol from filename
			String fileName;
			ArrayList fileList = new ArrayList();
			while ((fileName = read.readLine()) != null) {
				fileName = fileName.substring(7).replace("%20", " ");
				String test = fileName.substring(0, 9);
				if (test.equals("localhost")) {
					fileName = fileName.substring(9);
				}
				fileList.add(new File(fileName));
			}
			// Delete "localhost" from Mac filenames
			read.close();
			return fileList;
		}

		@Override
		public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
			if (dragItem != null) {
				if (comp.equals(dragItem) && !dragItem.dropLeft()) return false;
				return true;
			}
			return true;
		}

		class NXObjectTransferable implements Transferable {

			private NXObjectIndex[] objects;
			public boolean local;
			public String dir;

			public NXObjectTransferable(NXObjectIndex[] files, String dir, boolean local) {
				objects = files;
				this.dir = dir;
				this.local = local;
			}

			public synchronized Object getTransferData(DataFlavor flavor) throws IOException {
				Object o = null;
				if (flavor.equals(fileFlavor)) {
					java.util.List<File> files = new ArrayList<File>();
					File f; NXQueueItem qi; ArrayList downloadFiles = new ArrayList();
					for (int x = 0; x < objects.length; x++) {
						if (local) {
							f = new File(dir + "/" + objects[x].name);
						} else {
							f = new File(tempDir, objects[x].name);
							if (!f.exists()) {
								downloadFiles.add(objects[x]);
							}
						}
						files.add(f);
					}
					if (downloadFiles.size() > 0) {
						downloadFromIndex(dir, tempDir, downloadFiles.toArray(), null, true);
					}
					return files;
				} else {
					o = new Object[]{objects, dir, local};
				}
				return o;
			}

			public DataFlavor[] getTransferDataFlavors() {
				return new DataFlavor[]{localObjectFlavor, fileFlavor};
			}

			public boolean isDataFlavorSupported(DataFlavor flavor) {
				return flavor.equals(localObjectFlavor) || flavor.equals(fileFlavor);
			}
		}
	}

	public void dragGestureRecognized(DragGestureEvent dge) {
	}

	class UploadWorker extends SwingWorker {

		private NXQueueItem qi;

		protected UploadWorker(NXQueueItem qi) {
			this.qi = qi;
			setStatus("Uploading " + qi.fileName, false);
		}

		protected Object doInBackground() {
			try {
				ftpClient.changeFolder(qi.remoteFolder);
				ftpClient.put(qi.localFolder + "/" + qi.fileName, qi.fileName, progressUpdate);
				addToList();
			} catch (IOException ex) {
				String message = ex.getMessage();
				if (message.contains("550")) {
					NXQueueItem curr = (NXQueueItem) queue.get(0);
					curr.setError("You do not have permission to upload the file " + qi.fileName);
					queue.addError(curr);
				}
			} catch (Exception ex) {
				System.out.println("O NOES Cant change foldar!");
			}
			return new Object();
		}

		private void addToList() {
			File file = new File(qi.localFolder + "/" + qi.fileName);
			addItem(false, qi.remoteFolder, qi.fileName, new Date(file.lastModified()), file.length());
		}

		@Override
		public void done() {
			progressBar.setVisible(false);
			queue.process();
		}
	};

	private void addItem(boolean local, String folder, String file, Date modified, long size) {
		NXObjectIndex oi;
		//String[] elements = NXFormat.getNameFromPath(file);
		NXFtpBrowserPanel panel = (local) ? localPanel : remotePanel;
		NXListAdapter l = panel.getListIfShown(folder);

		String fakeDir = ftpClient.getCurrDir().equals("") ? "/" : ftpClient.getCurrDir();
		NXObjectIndex dirIndex = (NXObjectIndex) ftpClient.getObjectIndex().get(fakeDir);
		if (l != null) {
			oi = new NXObjectIndex('-', file, "wrxwrxwrx", "", "", size, 0, modified);
			l.addElement(oi);
			if (dirIndex != null) {
				dirIndex.objects.put(file, oi);
			}
		}
	}

	private void setStatus(String s, boolean indeterminate) {
		progressBar.setVisible(true);
		progressBar.setString(s);
		queue.currentString = s;
		progressBar.setIndeterminate(indeterminate);
	}

	private void updateOverall() {
		float progress = (float) queue.totalTransferred / (float) queue.totalSize * 100;
		overallProgressBar.setValue((int) progress);
		overallProgressBar.setVisible(true);
	}

	class FileProgressWatcher extends NXProgressUpdateRunnable {
		@Override
		public void run() {
			double progress = (double) bytes / (double) size * (double) 100;
			progressBar.setValue((int) progress);
			queue.updateTransferred(add);
		}
	}

	class DownloadWorker extends SwingWorker {

		private boolean preview;
		private NXObjectIndex obj;
		byte[] bytesOut;
		boolean error = false;
		long size;
		private boolean temp;
		File tempFile;
		private NXQueueItem qi;
		private String localFile;

		protected DownloadWorker(NXQueueItem qi) {
			setUp(qi);
		}

		protected DownloadWorker(NXQueueItem qi, boolean preview, boolean temp) {
			this.preview = preview;
			this.temp = temp;
			setUp(qi);
		}

		protected void setUp(NXQueueItem qi) {
			this.qi = qi;
			dragWait = null;
			if (temp) {
				try {
					tempFile = File.createTempFile("nixonftp", qi.fileName);
					localFile = tempFile.getAbsolutePath();
					tempFile.deleteOnExit();
				} catch (IOException ex) {
					System.out.println("DownloadWorker: IOException creating local temp file");
				}
			} else { this.localFile = qi.localFolder + "/" + qi.fileName; }
			setStatus("Downloading " + NXFormat.getNameFromPath(qi.fileName, false), false);
		}

		protected synchronized Object doInBackground() throws IOException {
			try {
				ftpClient.changeFolder(qi.remoteFolder);
				bytesOut = ftpClient.get(qi.fileName, localFile, preview, progressUpdate);
				dragWait = true;
			} catch (Exception e) {
				error = true;
				String message = e.getMessage();
				System.out.println("Error in DownloadWorker: " + message);
				if (message.contains("550")) {
					qi.setError("You do not have permission to download the file " + qi.fileName);
					queue.addError(qi);
				}
				dragWait = false;
			}
			return new Object();
		}

		@Override
		public void done() {
			progressBar.setVisible(false);
			queue.process();
			dragWait = !error;
			if (!error) {
				addItem(true, qi.localFolder, qi.fileName, new Date(), size);
			}

			if (temp) {
				openFile(localFile);
				java.util.Timer t = new java.util.Timer();
				t.schedule(new FileWatcher(tempFile, qi.remoteFolder, qi.fileName), 3000, 3000);
			}
			if (preview) {
				(new PreviewWorker(qi.remoteFolder, qi.fileName, bytesOut)).execute();
			}
		}
	};

	class FileWatcher extends TimerTask {
		File f;
		long lastModified;
		String origFolder;
		boolean pending;
		private final String remoteFile;
		
		public FileWatcher(File f, String remoteFolder, String remoteFile) {
			this.f = f;
			this.origFolder = remoteFolder;
			this.remoteFile = remoteFile;
			lastModified = f.lastModified();
		}

		@Override
		public void run() {
			long currentModified = f.lastModified();
			if (currentModified != lastModified) {
				String remotePath = origFolder + "/" + remoteFile;
				int result = uploadTempFile(f, remotePath);
				if (result == 0) { //upload it nao!
					queue.add(new NXQueueItem("upload", f.getAbsolutePath(), origFolder, remoteFile, null, f.length()), true);
					cancel();
				} else if (result == 1) {
					lastModified = currentModified;
				} else {
					f.delete();
					cancel();
				}
			}
		}

	}

	int uploadTempFile(File temp, String remoteFile) {

		int result = NXFormat.alertBox(this,
			"The local file representing " + NXFormat.getNameFromPath(remoteFile, false) + " has changed. Do you want to upload these changes?",
			"<b>Full remote file path: </b>" + remoteFile +
			"<br /><b>Modified: </b>" + NXFormat.parseDate(new Date(temp.lastModified())),
			JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
			new String[] {"Upload","Don't Upload Yet","Never Upload"}, 0);

		return result;
	}

	class PreviewWorker extends SwingWorker {

		private File file;
		private String data;
		boolean image = false;
		String title;
		private byte[] bytes;

		protected PreviewWorker(String remoteFolder, String remoteFile, byte[] bytes) {
			this.bytes = bytes;
			setUp(remoteFolder, remoteFile);
		}

		protected PreviewWorker(String remoteFolder, String remoteFile, File file) {
			this.file = file;
			setUp(remoteFolder, remoteFile);
		}

		private void setUp(String remoteFolder, String remoteFile) {
			image = NXFormat.isImage(remoteFolder + "/" + remoteFile);
			ImageIcon img = null;

			if (image) {
				if (file != null) {
					img = new ImageIcon(file.getAbsolutePath());
				} else if (bytes != null) {
					img = new ImageIcon(bytes);
				}
			}
			if (!(previewFrame instanceof NXSubFrame)) {
				previewFrame = new NXSubFrame(frame, title, image, img);
			} else {
				//previewFrame already created
				previewFrame.setTitle(title);
				//previewFrame.setUp(image, img);
				if (img != null) {
					previewFrame.setImage(img);
				}
			}
			previewFrame.setVisible(true);

			if (!image) {
				previewFrame.displayPlainText("");
			}
		}

		private boolean verifyLoad(long size) {
			Object[] options = {"Load",
				"Cancel"};
			int n = JOptionPane.showOptionDialog(frame,
					"<html><table width='360'><tr><td><strong>The file " + title + " may take a long time to preview. Would you still like to load it?</strong>"
					+ "</td></tr><tr><td>" + title + " is " + NXFormat.fileSize(size) + ". Text files larger than 128 KB need a lot of time to load.</td></tr></table></html>",
					null,
					JOptionPane.OK_CANCEL_OPTION,
					JOptionPane.WARNING_MESSAGE,
					null,
					options,
					options[1]);
			return (n == JOptionPane.OK_OPTION);
		}

		@Override
		protected Object doInBackground() throws Exception {
			if (!image && file != null) {
				if (file.length() > 131072) {
					if (!verifyLoad(file.length())) {
						return new Object();
					}
				}
				FileInputStream in = null;
				BufferedInputStream bis;
				DataInputStream dis = null;
				BufferedReader br = null;
				try {
					in = new FileInputStream(file);
					// Get the object of DataInputStream
					dis = new DataInputStream(in);
					br = new BufferedReader(new InputStreamReader(in));
					String strLine;
					//Read File Line By Line
					while ((strLine = br.readLine()) != null) {
						// Print the content on the console
						data += strLine + "\n";
					}
					//Close the input stream

				} finally {
					dis.close();
					in.close();
					br.close();
				}
			}
			return new Object();
		}

		@Override
		public void done() {
			if (data == null && bytes != null) {
				data = new String(bytes);
			}
			if (data != null && image == false) {
				if (bytes.length > 131072) {
					if (!verifyLoad(bytes.length)) {
						return;
					}
				}
				previewFrame.displayPlainText(data);
			}
		}
	};

	//only used for removing and creating directories on server!
	class CommandWorker extends SwingWorker {

		private NXQueueItem qi;
		private final boolean explicit;
		private String error;

		protected CommandWorker(NXQueueItem qi, String message, boolean explicit) {
			this.qi = qi;
			this.explicit = explicit;
			setStatus(message, true);
		}

		@Override
		protected Object doInBackground() throws Exception {
			String[] sError = new String[2];
			//localFolder for command

			try {
				String command = qi.localFolder;
				boolean goUp = command.equals("CDUP");
				boolean rmd = command.equals("RMD");
				if (!goUp) ftpClient.changeFolder(qi.remoteFolder);
				if (command.equals("MKD")) {
					ftpClient.createDirectory(qi.fileName);
				} else if (rmd) {
					ftpClient.deleteDirectory(qi.fileName);
				} else if (goUp) {
					ftpClient.cdUp();
				}
			} catch (Exception e) {
				String msg = e.getMessage();
				if (qi.localFolder.equals("MKD") && !explicit) {
					error = "You do not have permission to create the folder \"" + qi.fileName + "\" here.";
					if (explicit) {
						if (msg.contains("550")) {
							sError[0] = "Could not create new folder.";
							sError[1] = error;
						}
					}
				} else if (qi.localFolder.equals("RMD")) {
					error = "You do not have permission to delete the folder.";
					if (explicit) {
						if (msg.contains("550")) {
							sError[0] = "Could not delete the folder \"" +  qi.fileName + "\".";
							sError[1] = error;
						}
					}
				}
				if (!explicit && error != null) {
					qi.setError(error);
					queue.addError(qi);
				}
				if (sError[0] != null) {
					showError(sError);
				}
			}
			return new Object();
		}

		@Override
		public void done() {
			progressBar.setVisible(false);
			overallProgressBar.setVisible(false);
			if (error == null && qi.localFolder.equals("MKD")) {
				NXListAdapter list = remotePanel.getListIfShown(qi.remoteFolder);
				if (list != null) {
					list.addElement(new NXObjectIndex('d', qi.fileName));
				}
			}
			queue.process();
		}
	};

	//only used for removing and creating directories on server!
	class TypeWorker extends SwingWorker {

		private NXQueueItem qi;
		private int type = -1;

		protected TypeWorker(NXQueueItem qi) {
			this.qi = qi;
			setStatus("Checking type of link \"" + qi.fileName + "\"", true);
		}

		@Override
		protected Object doInBackground() throws Exception {
			String[] sError = new String[2];
			//localFolder for command
			try {
				type = getType(qi.remoteFolder, (NXObjectIndex) qi.values[0]);
				
			} catch (Exception e) {
				String msg = e.getMessage();
				sError[0] = "Could not determine the type of " + qi.fileName + ".";
				sError[1] = "O NOES";
					showError(sError);
			}
			return new Object();
		}

		@Override
		public void done() {
			progressBar.setVisible(false);
			overallProgressBar.setVisible(false);

			String command = qi.activity.substring(5);
			if (command.equals("list")) {
				//link must be a folder!
				if (type == 2) {
					queue.add(new NXQueueItem("list", "", qi.remoteFolder + '/' + qi.fileName, "", qi.display, 0), true);
				} else {
					if (!remotePanel.listing) remotePanel.removeList(-1);
					else remotePanel.table.setDirectory(qi.remoteFolder);
				}
			} else if (command.equals("download")) {
				
				if (type == 0) { //link is a file
					queue.add(new NXQueueItem("download", qi.localFolder, qi.remoteFolder, qi.fileName), true);
				} else {
					downloadFromIndex(qi.remoteFolder, qi.localFolder, qi.values, (NXListAdapter) qi.display);
				}
			} else if (command.equals("upload")) {
				if (type == 0) { //link is a file
					queue.add(new NXQueueItem("upload", qi.localFolder, qi.remoteFolder, qi.fileName), true);
				} else {
					uploadFromIndex(qi.remoteFolder, qi.localFolder, qi.values, (NXListAdapter) qi.display);
				}
			}
			queue.process();
		}
	};

	class RenameWorker extends SwingWorker {

		private NXQueueItem qi;
		private boolean move;

		protected RenameWorker(NXQueueItem qi) {
			this.qi = qi;
			setStatus("Renaming \"" + qi.fileName + "\"", true);
		}

		protected RenameWorker(NXQueueItem qi, boolean move) {
			this(qi);
			this.move = true;
		}

		@Override
		protected Object doInBackground() throws Exception {
			String[] sError = new String[2];

			try {
				//for remote rename operations, localFolder variable is used for remote Folder
				ftpClient.changeFolder(qi.localFolder);
				// ^ remoteFolder = old filename, fileName = new filename
				ftpClient.renameImpl(qi.remoteFolder, qi.fileName);
			} catch (Exception e) {
				String msg = e.getMessage();
				if (msg.contains("550")) {
					sError[0] = "Could not rename the file \""
							+ qi.remoteFolder + "\" to \""
							+ qi.fileName + "\".";
					sError[1] = "You do not have permission to rename files in \""
							+ qi.localFolder + "\".";
				}
				if (sError[0] != null) {
					showError(sError);
				}
			}
			return new Object();
		}

		@Override
		public void done() {
			reloadList(remotePanel.getListIfShown(qi.localFolder)); //localFolder is remote folder here!
			queue.process();
		}
	};

	class DeleteWorker extends SwingWorker {

		private NXQueueItem qi;

		protected DeleteWorker(NXQueueItem qi) {
			this.qi = qi;
			setStatus("Deleting \"" + qi.fileName + "\".", true);
		}

		@Override
		protected Object doInBackground() throws Exception {
			deleteRemoteFile(qi.remoteFolder, qi.fileName);
			return new Object();
		}

		@Override
		public void done() {
			queue.process();
		}
	};

	void deleteRemoteFile(String remoteFolder, String remoteFile) {
		String[] sError = new String[2];

		try {
			ftpClient.changeFolder(remoteFolder);
			ftpClient.delete(remoteFile);
		} catch (Exception e) {
			String msg = e.getMessage();
			if (msg.contains("550")) {
				NXQueueItem curr = new NXQueueItem("delete", "", remoteFolder, remoteFile);
				curr.setError("You do not have permission to delete the file " + remoteFile);
				queue.addError(curr);
			}
		}
	}

	class LocalDeleteWorker extends SwingWorker {

		private String initialPath;
		private NXListAdapter list;
		private char type = '-';
		private boolean success;

		protected LocalDeleteWorker(String initialPath, NXListAdapter list) {
			super();
			this.initialPath = initialPath;
			this.list = list;
			setStatus("Deleting \"" + NXFormat.getNameFromPath(initialPath, false) + "\".", true);
		}

		@Override
		protected Object doInBackground() throws Exception {
			deleteRecursively(initialPath);
			return new Object();
		}

		private void deleteRecursively(String path) {
			File f = new File(path);
			if (f.isDirectory()) {
				type = 'd';
				File[] files = f.listFiles();
				for (int x = 0; x < files.length; x++) {
					if (files[x].isDirectory()) {
						deleteRecursively(files[x].getPath());
					} else {
						files[x].delete();
					}
				}
			}
			boolean b = f.delete();
			if (path.equals(initialPath)) {
				success = b;
			}
		}

		@Override
		public void done() {
			if (success) {
				NXObjectIndex[] items = new NXObjectIndex[1];
				items[0] = new NXObjectIndex(type, NXFormat.getNameFromPath(initialPath, false));
				localPanel.remoteItemsFromList(list, items);
				if (type == 'd') {
					localPanel.removeList(list.getIndex() + 1);
				}
			}
			progressBar.setVisible(false);
		}
	};

	private void showError(String[] s) {
		NXMessageLabel lbl = new NXMessageLabel();
		lbl.setInfo(s[0], s[1]);
		JOptionPane.showMessageDialog(frame, lbl,
				"", JOptionPane.ERROR_MESSAGE);
	}

	private void doTable(NXTable t) {
		t.addMouseListener(new ListListener(t.getLocal()));
		t.addKeyListener(new ListListener(t.getLocal()));
		setMenu(t, t.getLocal());
		t.setTransferHandler(new NXListTransferHandler(t.getLocal(), true));
		try {
			t.getDropTarget().addDropTargetListener(new NXDropListener(t));
		} catch (TooManyListenersException ex) {
		}
	}

	private void doCrumbs(String dir, boolean local) {
		NXFtpBrowserPanel panel = null;
		panel = (local) ? localPanel : remotePanel;
		if (local) {
			dir = dir.replace("\\", "/");
		}
		panel.crumbPanel.removeAll();
		String[] dirs = dir.split("/");
		String lastDir = "";
		String currDir;
		NXCrumb crumb;
		boolean first;
		for (int x = 0; x < dirs.length; x++) {
			first = dirs[x].equals("");
			if (first) {
				currDir = (local) ? "" : "/";
			} else {
				currDir = dirs[x];
			}
			crumb = new NXCrumb(currDir, lastDir + currDir);
			crumb.first = first;
			if (first && local) {
				crumb.setText("Computer");
			}
			crumb.addMouseListener(new CrumbListener(local));
			panel.crumbPanel.add(crumb);
			lastDir += dirs[x] + "/";
		}
		panel.crumbPanel.updateUI();
		panel.table.setDirectory(dir);
	}

	class CrumbListener implements MouseListener {

		private boolean local;

		public CrumbListener(boolean local) {
			this.local = local;
		}

		public void mouseClicked(MouseEvent e) {
			if (e.getButton() == MouseEvent.BUTTON3) {
				return;
			}
			NXCrumb crumb = (NXCrumb) e.getComponent();
			NXFtpBrowserPanel panel = (local) ? localPanel : remotePanel;
			String dir = (crumb.dir.equals("/") || crumb.first) ? "" : crumb.dir;
			panel.table.setDirectory(dir);

			if (local) {
				(new ListingWorker(dir, true)).execute();
			} else {
				HashMap map = ftpClient.getObjectIndex();
				if (map.containsKey(crumb.dir)) {
					updateList(null,
							(NXObjectIndex) ftpClient.getObjectIndex().get(crumb.dir), local);
				} else {
					queue.add(new NXQueueItem("list", "", crumb.dir, "", null, 0), true);
				}
			}

			doCrumbs(dir, local);
		}

		public void mousePressed(MouseEvent e) {
			if (e.getButton() == MouseEvent.BUTTON3) {
				JPopupMenu menu = new JPopupMenu();
				HashMap map = ftpClient.getObjectIndex();
				NXCrumb lbl = (NXCrumb) e.getComponent();
				if (!local) {
					NXObjectIndex oi = (NXObjectIndex) ftpClient.getObjectIndex().get(lbl.dir);
					NXObjectIndex obj;
					NXTraverseMenu m;
					String dir = (lbl.dir.equals("/")) ? "" : lbl.dir;
					Object[] objects = oi.objects.values().toArray();
					for (int x = 0; x < objects.length; x++) {
						obj = (NXObjectIndex) objects[x];
						if (obj.type == 2 || obj.realType == 2) {
							m = new NXTraverseMenu(obj.name, dir + "/" + obj.name, false);
							m.setIcon(NXIcon.small[2]);
							m.addMenuListener(new ListTraverseMenuListener());
							menu.add(m);
						}
					}
				}
				menu.show(lbl, 0, lbl.getHeight());
			}
		}

		public void mouseReleased(MouseEvent e) {
		}

		public void mouseEntered(MouseEvent e) {
		}

		public void mouseExited(MouseEvent e) {
		}
	}

	private void addRemoteList(String dir, int index) {
		if (remotePanel.listing) {
			return;
		}
		remotePanel.removeList(index);
		remotePanel.addList();
		final NXList list = remotePanel.getListBoxes(-1);
		list.setDirectory(dir);
		list.addMouseListener(new ListListener(false));
		list.addKeyListener(new ListListener(false));
		list.setTransferHandler(new NXListTransferHandler(false, false));
		try {
			list.getDropTarget().addDropTargetListener(new NXDropListener(list));
		} catch (TooManyListenersException ex) {
		}
		//list.setTransferHandler(new ListTransferHandler());
		DragSource ds = DragSource.getDefaultDragSource();
	  DragGestureRecognizer dgr = ds.createDefaultDragGestureRecognizer(
			list,
			DnDConstants.ACTION_MOVE,
			this);
		setMenu(list, false);
	}

	private void addUniqueLocation(NXTraverseMenu menu, boolean local) {
		boolean conflict = false;
		NXFtpBrowserPanel panel = (local) ? localPanel : remotePanel;
		String dir;
		for (int x = 0; x < panel.locations.size(); x++) {
			dir = panel.locations.get(x).getDir();
			if (menu.getDir().contains(dir)) {
				conflict = true; //already added in subfolder!
			}
		}
		if (!conflict) {
			panel.locations.add(menu);
		}
	}

	class TransferToActionListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			NXTraverseMenu menu;
			if (e.getSource() instanceof NXMenuItem) {
				NXMenuItem item = (NXMenuItem) e.getSource();
				menu = (NXTraverseMenu) item.parent;
			} else {
				menu = (NXTraverseMenu) e.getSource();
			}

			addUniqueLocation(menu, menuList.getLocal());
			NXListAdapter otherList;
			NXFtpBrowserPanel panel = (menuList.getLocal()) ? remotePanel : localPanel;
			otherList = panel.getListIfShown(menu.getDir());
			if (menuList.getLocal()) {
				uploadFromIndex(menu.getDir(), menuList.getDirectory(), menuList.getSelectedValues(), otherList);
			} else {
				downloadFromIndex(menuList.getDirectory(), menu.getDir(), menuList.getSelectedValues(), otherList);
			}
			contextMenu.setVisible(false);
			contextMenu.updateUI();
		}
	}

	class TraverseMenuListener implements MenuListener {
		ActionListener listener;

		public TraverseMenuListener(ActionListener listener) {
			this.listener = listener;
		}

		private void addHere(NXTraverseMenu topMenu) {
			NXMenuItem here = new NXMenuItem("Here", "Here");
			topMenu.add(here);
			here.parent = topMenu;
			here.addActionListener(listener);
		}

		private void removeItems(NXTraverseMenu topMenu) {
			Component[] components = topMenu.getMenuComponents();

			for (int x = 0; x < components.length; x++) {
				if (components[x] instanceof AbstractButton) {
					((AbstractButton)components[x]).removeActionListener(listener);
				}
			}
			topMenu.removeAll();
		}

		void showLocalMenu(String root, NXTraverseMenu topMenu) {
			removeItems(topMenu);
			if (ftpClient == null) {
				return;
			}

			addHere(topMenu);

			NXObjectIndex rootIndex = (NXObjectIndex) ftpClient.getObjectIndex().get(root);
			if (rootIndex != null) {
				Object[] folderContents = rootIndex.objects.values().toArray();
				String name;
				String path;
				for (int x = 0; x < rootIndex.objects.size(); x++) {
					NXObjectIndex oi = (NXObjectIndex) folderContents[x];
					path = (root.equals("/")) ? '/' + oi.name : root + '/' + oi.name;
					name = NXFormat.getNameFromPath(path, false);
					if (oi.type == 2 || oi.realType == 2) {
						NXTraverseMenu newMenu = new NXTraverseMenu(name, path, false);
						newMenu.addMenuListener(this);
						topMenu.add(newMenu);
					}
				}
			}
			addAction(topMenu);
		}

		public void showMenu(String root, NXTraverseMenu topMenu) {
			topMenu.removeAll();

			addHere(topMenu);

			File f = new File(root);
			File[] files = f.listFiles(new HiddenFileFilter());
			if (files == null) {
				return;
			}
			String name;
			String path;
			for (int x = 0; x < files.length; x++) {
				path = files[x].getPath();
				name = NXFormat.getNameFromPath(path, false);
				if (files[x].isDirectory()) {
					NXTraverseMenu newMenu = new NXTraverseMenu(name, path, false);
					newMenu.addMenuListener(this);
					topMenu.add(newMenu);
				}
			}
			addAction(topMenu);
		}

		private void addAction(NXTraverseMenu menu) {
			menu.removeActionListener(listener);
			menu.addActionListener(listener);
		}

		public void doMenu(EventObject e) {
			NXTraverseMenu m = (NXTraverseMenu) e.getSource();
			String dir = m.getDir();
			if (!menuList.getLocal()) {
				if (dir.equals("")) { //initial
					Object[] roots = drives.objects.values().toArray();
					m.removeAll();
					String path;
					boolean home;
					NXFtpBrowserPanel panel = (menuList.getLocal()) ? localPanel : remotePanel;
					int size = panel.locations.size();
					for (int x = 0; x < size; x++) {
						m.add(panel.locations.get(x));
					}
					if (size > 0) {
						m.addSeparator();
					}
					for (int x = 0; x < roots.length; x++) {
						NXObjectIndex oi = (NXObjectIndex) roots[x];
						home = oi.type == 4;
						path = home ? System.getProperty("user.home") : oi.name + '/';
						NXTraverseMenu newMenu = new NXTraverseMenu(oi.name, path, true);
						newMenu.setIcon((home) ? NXIcon.small[4] : NXIcon.small[3]);
						newMenu.addMenuListener(this);
						m.add(newMenu);
					}
				} else {
					showMenu(m.getDir(), m);
				}
			} else {
				if (dir.equals("")) { //inital
					showLocalMenu("/", m);
				} else {
					showLocalMenu(dir, m);
				}
			}
		}

		public void menuSelected(MenuEvent e) {
			throw new UnsupportedOperationException("Not supported yet.");
		}

		public void menuDeselected(MenuEvent e) {
			throw new UnsupportedOperationException("Not supported yet.");
		}

		public void menuCanceled(MenuEvent e) {
			throw new UnsupportedOperationException("Not supported yet.");
		}
	}

	class ListTraverseMenuListener extends TraverseMenuListener implements MenuListener {

		public ListTraverseMenuListener() {
			super(new TransferToActionListener());
		}

		@Override
		public void menuCanceled(MenuEvent evt) {
		}

		@Override
		public void menuDeselected(MenuEvent evt) {
		}

		public void menuSelected(MenuEvent evt) {
			doMenu(evt);
		}
	}

	class CrumbMenuListener extends TraverseMenuListener implements PopupMenuListener {

		public CrumbMenuListener() {
			super(null);
			//this.listener = crumbActionListener;
		}

		public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
			doMenu(e);
		}

		public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
		}

		public void popupMenuCanceled(PopupMenuEvent e) {
		}
	}

	class HiddenFileFilter implements FilenameFilter {

		public boolean accept(File f, String name) {
			return !new File(f.getPath() + '/' + name).isHidden();
		}
	}

	private void addLocalList(String dir, int index) {
		if (localPanel.listing) {
			return;
		}
		localPanel.removeList(index);
		localPanel.addList();
		NXList list = localPanel.getListBoxes(-1);
		list.setDirectory(dir);
		list.setTransferHandler(new NXListTransferHandler(true, false));
		try {
			list.getDropTarget().addDropTargetListener(new NXDropListener(list));
		} catch (TooManyListenersException ex) {
		}
		list.addMouseListener(new ListListener(true));
		list.addKeyListener(new ListListener(true));
		setMenu(list, true);
	}

	private void setMenu(NXListAdapter list, boolean local) {
		MouseListener popupListener = new PopupListener();
		list.addMouseListener(popupListener);
	}

	class ListListener implements MouseListener, KeyListener {

		private boolean local;
		private NXFtpBrowserPanel panel;

		public ListListener(boolean local) {
			super();
			this.local = local;
			if (local) {
				this.panel = localPanel;
			} else {
				this.panel = remotePanel;
			}
		}

		public void mouseClicked(MouseEvent e) {
			NXListAdapter adapter = (NXListAdapter) e.getComponent();
			if (e.getButton() != MouseEvent.BUTTON3) {
				listAction(adapter, e.getClickCount() - 1);
			}
		}

		private void listAction(NXListAdapter adapter, int eventType) { // type 0 for single-click; 1 for double
			NXObjectIndex selectedObject = (NXObjectIndex) adapter.getSelectedValue();

			boolean quit = panel.table.dirty && panel.listing;
			if (selectedObject == null || quit) {
				return;
			}
			NXListAdapter list = (panel.listing) ? null : adapter;
			String dir = null;

			if (adapter.getSelectedItemCount() == 1) {
				boolean home = selectedObject.type == 4;
				dir = home ? //home directory?
						System.getProperty("user.home") : adapter.getDirectory() + "/" + selectedObject.name;
				int type = selectedObject.type;
				if (eventType == 0) {
					if (type >= 1) { //directory

						if (local) {
							if (!panel.listing) {
								addLocalList(dir, list.getIndex() + 1);
							}
							(new ListingWorker(dir, true)).execute();
						} else { //remote
							NXListAdapter update = doFolder(dir, list);
							HashMap map = ftpClient.getObjectIndex();
							
							//need to find out type of link before listing
							if (selectedObject.type == 1 && selectedObject.realType == -1) {
								NXQueueItem qi = new NXQueueItem("type list", "", adapter.getDirectory(), selectedObject.name, update, 0);
								qi.display = update;
								qi.values = new NXObjectIndex[] { selectedObject };
								qi.fakePath = selectedObject.realLoc;
								queue.add(qi, true);
							} else if (map.containsKey(dir) || map.containsKey(selectedObject.realLoc)) {
								NXObjectIndex oi = (NXObjectIndex) map.get(dir);
								updateList(update,
										oi, local);
								preloadDirectory(dir, oi.objects);
							} else {
								NXQueueItem qi = new NXQueueItem("list", "", dir, "", update, 0);
								queue.add(qi, true);
								if (panel.listing) {
									panel.table.dirty = true;
								}
							}
						}
						if (panel.listing) {
							if (home) {
								dir = "/" + dir;
							}
							doCrumbs(dir, local);
						}
					} else if (type == 0) {
						//check if image
						if (previewFrame != null && previewFrame.isVisible()) {
							previewImage(adapter.getDirectory(), dir, local);
						}
						if (!panel.listing) {
							panel.removeList(list.getIndex() + 1);
						}
					}
					if (mfrDetails != null) {
						mfrDetails.setDirectory(adapter.getDirectory());
						mfrDetails.setObject(selectedObject);
					}
				} else if (eventType == 1 && type == 0) { //double-click on file
					if (local) { openFile(dir);
					} else {
						//previewImage(dir, local);
						queue.add(new NXQueueItem("open",
								  "",
								  adapter.getDirectory(),
								  selectedObject.name,
								  null,
								  selectedObject.size
								  ), true);
					}
				}
			}
		}

		public void mousePressed(MouseEvent e) {
		}

		public void mouseReleased(MouseEvent e) {
		}

		public void mouseEntered(MouseEvent e) {
		}

		public void mouseExited(MouseEvent e) {
		}

		public void keyTyped(KeyEvent e) {
		}

		public void keyPressed(KeyEvent e) {
		}

		public void keyReleased(KeyEvent e) {
			NXListAdapter list = (NXListAdapter) e.getComponent();
			switch (e.getKeyCode()) {
				case KeyEvent.VK_DELETE:
					deleteFromList(list, local);
					break;
				case KeyEvent.VK_CONTEXT_MENU:
				case KeyEvent.VK_F10:
					showPopup(list, 0, list.getRealBounds().height);
					break;
				case KeyEvent.VK_LEFT:
					NXList l = panel.getListBoxes(list.getIndex() - 1);
					l.requestFocusInWindow();
					panel.browserScroll.getViewport().scrollRectToVisible(l.getRealBounds());
					break;
				case KeyEvent.VK_ENTER:
				case KeyEvent.VK_RIGHT:
					int type = (e.isControlDown()) ? 1 : 0;
					listAction(list, type);
					break;
			}
		}
	}

	private NXListAdapter doFolder(String dir, NXListAdapter list) {
		NXFtpBrowserPanel panel;
		if (list != null && list.getLocal()) panel = localPanel;
		else panel = remotePanel;
		
		NXListAdapter ret;
		if (!panel.listing) {
			addRemoteList(dir, list.getIndex() + 1);
			ret = panel.getListBoxes(-1);
		} else {
			ret = panel.table;
		}
		return ret;
	}

	class PopupListener extends MouseAdapter {

		@Override
		public void mousePressed(MouseEvent e) {
			maybeShowPopup(e);
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			maybeShowPopup(e);
		}

		private void maybeShowPopup(MouseEvent e) {
			if (e.getButton() == 3) {
				NXListAdapter c = (NXListAdapter) e.getComponent();
				showPopup(c, e.getX(), e.getY());
			}
		}
	}

	void showPopup(NXListAdapter c, int posX, int posY) {
		menuList = c;
		int selDiff = c.getSelectedItemCount();

		HashMap idx = contextMenu.getIndex();

		NXObjectIndex selectedObject = (NXObjectIndex) c.getSelectedValue();
		NXMenuItem newFolder = (NXMenuItem) contextMenu.getIndex().get("NewFolder");
		NXMenuItem newFolderInItem = (NXMenuItem) contextMenu.getIndex().get("NewFolderIn");
		NXMenuItem preview = (NXMenuItem) idx.get("Preview");
		NXMenuItem transfer = (NXMenuItem) idx.get("Transfer");
		NXMenuItem switchView = (NXMenuItem) idx.get("SwitchView");
		NXTraverseMenu transferTo = (NXTraverseMenu) idx.get("TransferTo");

		NXMenuItem rename = (NXMenuItem) idx.get("Rename");
		JMenu deleteMenu = contextMenu.getDeleteMenu();

		newFolderInItem.setVisible(false);
		//preview.setVisible(false);

		contextMenu.switchView(c.getLocal() ? localPanel.listing : remotePanel.listing);

		String appe;
		boolean selection = selectedObject != null;

		if (selection) {
			if (selDiff == 1) { // 1 item selected
				if (selectedObject.type == 2 || selectedObject.realType == 2) {
					newFolderInItem.setText("New Folder in " + selectedObject.name);
					newFolderInItem.setVisible(true);
				} else if (selectedObject.type == 0 || selectedObject.realType == 0) {
					if (selectedObject.size <= 524288) {
						preview.setVisible(true);
					}
				}
				appe = " \"" + selectedObject.name + "\"";
			} else { // more than 1 item selected
				appe = " these " + selDiff + " items";
			}
			deleteMenu.setText("Delete" + appe);
			String strTransfer = (c.getLocal()) ? "Upload" : "Download";
			transfer.setText(strTransfer + appe);
			transferTo.setText(strTransfer + " To");
			rename.setText("Rename" + appe);
		}
		transfer.setVisible(selection);
		transferTo.setVisible(selection);

		HashMap separators = contextMenu.getSeparators();
		//((JSeparator) separators.get("bottom")).setVisible(selection);
		((JSeparator) separators.get("middle")).setVisible(selection);
		deleteMenu.setVisible(selection);
		rename.setVisible(selection);

		contextMenu.setLocal(c.getLocal());
		contextMenu.show((Component) c, posX, posY);
	}

	class ListPopupListener implements ActionListener {
		private String id;
		private NXObjectIndex selectedObject;

		public void actionPerformed(ActionEvent e) {
			NXMenuItem menuItem = (NXMenuItem) e.getSource();

			selectedObject = (NXObjectIndex) menuList.getSelectedValue();

			id = menuItem.getId();
			if (id.contains("NewFolder")) {
				newFolder();
				/*} else if (id.equals("ShowLog")) {
				showLog();
				} else if (id.equals("ShowQueue")) {
				showQueue();
				} else if (id.equals("Filters")) {
				doFilters();*/
			} else if (id.equals("Preview")) {
				preview();
			} else if (id.equals("Open")) {
				open();
			} else if (id.equals("Details")) {
				details();
			} else if (id.equals("Rename")) {
				rename();
			} else if (id.equals("Delete")) {
				deleteFromList(menuList, menuList.getLocal());
			} else if (id.equals("Transfer")) {
				transfer();
			} else if (id.equals("Reload")) {
				reload();
			} else if (id.equals("SwitchView")) {
				NXFtpBrowserPanel panel = menuList.getLocal() ? localPanel : remotePanel;
				doListing(menuList.getLocal(), !panel.listing);
			}
		}

		private void details() {
			if (mfrDetails == null) {
				initDetailsDialog();
			}
			mfrDetails.setVisible(true);
			mfrDetails.setDirectory(menuList.getDirectory());
			mfrDetails.setObject(selectedObject);
		}

		private void initDetailsDialog() {
			mfrDetails = new NXDetailsDialog(frame); //frame may be null if applet
			if (frame != null) mfrDetails.setLocationRelativeTo(frame);
			mfrDetails.setVisible(true);
		}

		private void rename() {
			String currFile = menuList.getDirectory() + '/' + selectedObject.name;

			String newName = (String) JOptionPane.showInputDialog(
					frame,
					"Enter the new name of the file \"" + selectedObject.name + "\".",
					"Rename",
					JOptionPane.QUESTION_MESSAGE,
					null,
					null,
					selectedObject.name);

			String fullPath = menuList.getDirectory() + '/' + newName;
			if (newName != null && !newName.equals(selectedObject.name)) {
				if (!menuList.getLocal()) {
					queue.add(new NXQueueItem("rename", menuList.getDirectory(), selectedObject.name, newName, null, 0), true);
				} else {
					File f = new File(currFile);
					boolean success = f.renameTo(new File(fullPath));
					if (success) {
						selectedObject.name = newName;
						menuList.updateUI();
					} else { //could not rename
						NXMessageLabel lbl = new NXMessageLabel();
						lbl.setInfo("Could not rename \"" + selectedObject.name + "\" to \"" + newName + "\".",
								"Make sure there is not already a file or folder with the same name in \"" + menuList.getDirectory() + "\".");
						JOptionPane.showMessageDialog(frame, lbl,
								"", JOptionPane.ERROR_MESSAGE);
					}
				}
			}
		}

		private void newFolder() {
			String shortDir;
			String initialDir = menuList.getDirectory();
			boolean in;

			in = id.equals("NewFolderIn");
			if (in) {
				initialDir += '/' + selectedObject.name;
				shortDir = selectedObject.name;
			} else {
				if (menuList.getDirectory().equals("")) {
					shortDir = "/";
				} else {
					shortDir = NXFormat.getNameFromPath(menuList.getDirectory(), false);
				}
			}

			String newDir = (String) JOptionPane.showInputDialog(
					frame,
					"Enter the name of the folder that will be created in: \n"
					+ "<html><font face='Helvetica' size='+1'>" + shortDir + "</font><br>"
					+ "<font color='#336699' size='-2'>" + initialDir + "</font></html>\n",
					"Create New Folder",
					JOptionPane.QUESTION_MESSAGE,
					NXIcon.folder,
					null,
					"");
			if (newDir != null) {
				NXObjectIndex dir = new NXObjectIndex('d', newDir, "rwxrwxrwx", "", "", 0, 0, new Date());
				NXListAdapter updateList;

				if (!menuList.getLocal()) {
					queue.add(new NXQueueItem("mkdir-explicit", "", initialDir, newDir, menuList, 0), true);
				} else {
					String newPath = initialDir + '/' + newDir;
					File f = new File(newPath);
					f.mkdir();
					if (in) {
						updateList = localPanel.getListBoxes(-1);
					} else {
						updateList = menuList;
					}
					NXObjectIndex[] dirs = new NXObjectIndex[1];
					dirs[0] = dir;
					localPanel.addItemsToList(updateList, dirs);
				}
			}
		} //newFolder

		private void preview() {
			previewImage(menuList.getDirectory(), selectedObject.name, menuList.getLocal());
		}

		private void open() {
			openFile(menuList.getDirectory() + '/' + selectedObject.name);
		}

		private void transfer() {
			String toBase;
			NXListAdapter dest;
			NXFtpBrowserPanel panel;

			panel = (menuList.getLocal()) ? remotePanel : localPanel;
			dest = (panel.listing) ? panel.table : panel.getListBoxes(-1);

			if (!menuList.getLocal()) {
				toBase = dest.getDirectory();
				downloadFromIndex(menuList.getDirectory(), toBase, menuList.getSelectedValues(), dest);
			} else {
				toBase = remotePanel.getListBoxes(-1).getDirectory();
				uploadFromIndex(toBase, menuList.getDirectory(), menuList.getSelectedValues(), dest);
			}
		}

		private void reload() {
			reloadList(menuList);
		}

		private void showQueue() {
			if (!queuePanel.isVisible()) {
				queuePanel.setVisible(true);
			}
		}
	}

	private void openFile(String file) {
		try {
			Desktop.getDesktop().open(new File(file));
		} catch (IOException ex) {
		}
	}

	private void deleteFromList(NXListAdapter list, boolean local) {
		Object[] values = list.getSelectedValues();
		if (!local) {
			NXQueueItem qi = new NXQueueItem("deletefromindex", "", list.getDirectory(), "");
			qi.values = values;
			qi.setDisplay(list);
			queue.add(qi, false);
		} else {
			for (int x = 0; x < values.length; x++) {
				(new LocalDeleteWorker(list.getDirectory() + '/' + ((NXObjectIndex) values[x]).name, list)).execute();
			}
		}
	}

	private void reloadList(NXListAdapter list) {
		if (list == null) return;
		if (list.getLocal()) {
			(new ListingWorker(list.getDirectory(), true, list)).execute();
		} else {
			String dir = list.getDirectory();
			if (dir.equals("")) {
				dir = "/";
			}
			queue.add(new NXQueueItem("list", "", dir, "", list, 0), false);
		}
	}

	private void doFilters() {
		NXFilterDialog dlg = new NXFilterDialog(frame);
		dlg.setFilters(remotePanel.getFilters());
		dlg.setModal(true);
		dlg.setLocationRelativeTo(frame);
		dlg.setVisible(true);
		NXFilter[] filters = dlg.getFilters();
		remotePanel.setFilters(filters);
		localPanel.setFilters(filters);
	}

	private void previewImage(String path, String fileName, boolean local) {
		try {
			if (!local) {
				queue.add(new NXQueueItem("preview", "", path, fileName), true);
			} else {
				(new PreviewWorker(path, fileName, new File(path + "/" + fileName))).execute();
			}
		} catch (Exception ex) {
			System.out.println("OH SHIT");
		}
	}

	private void downloadFromIndex(String base, String localBase, Object[] index, NXListAdapter list) {
		downloadFromIndex(base, localBase, index, list, false);
	}

	private void downloadFromIndex(String base, String localBase, Object[] index, NXListAdapter list, boolean blocking) {
		NXObjectIndex oi;
		String pwf;
		String localFile;
		File f = new File(localBase);
		ArrayList items = new ArrayList();

		if (!f.exists()) {
			f.mkdir();
		}
		int type;
		for (int x = 0; x < index.length; x++) {
			oi = (NXObjectIndex) index[x];
			pwf = base + '/' + oi.name;
			localFile = localBase + '/' + oi.name;
			type = getType(base, oi);
			if (type > 0) {
				if (list != null) {
					list.addElement(new NXObjectIndex('d', oi.name));
				}
				NXQueueItem item = new NXQueueItem("index", localFile, pwf, "");
				if (oi.type == 1) { //link
					item.fakePath = oi.realLoc;
				}
				queue.add(item, false);
			} else {
				if (queue.find("download", localBase, base, oi.name) != null) continue;
				dragWait = true;
				queue.add(new NXQueueItem("download", localBase, base, oi.name, list, oi.size), false);
				if (blocking) {
					while (dragWait == null) {}
					dragWait = null;
				}
			}
		}
	}

	private void createDirectoryIf(String base) {
		String indexBase;
		String[] pathElements = NXFormat.getNameFromPath(base);
		if (pathElements[1] == null) {
			return;
		}
		indexBase = base.equals("") ? "/" : pathElements[0];
		NXObjectIndex check = (NXObjectIndex) ftpClient.getObjectIndex().get(indexBase);
		if (check == null || !check.objects.containsKey(pathElements[1])) {
			queue.add(new NXQueueItem("mkdir", "", pathElements[0], pathElements[1]), false);
		}
	}

	private void uploadFromIndex(String base, String localBase, Object[] index, Object list) {
		NXObjectIndex oi;
		String pwf;
		String localFile;
		File file;
		String folderFileName;

		char type; int inType; File folder; File[] files; NXObjectIndex[] folderObj;
		createDirectoryIf(base);
		for (int x = 0; x < index.length; x++) {
			oi = (NXObjectIndex) index[x];
			pwf = base + '/' + oi.name;
			localFile = localBase + '/' + oi.name;
			inType = getType(base, oi);
			if (inType > 0) { //folder
				folder = new File(localBase + '/' + oi.name);
				files = folder.listFiles();
				folderObj = fileListToIndex(files);
				uploadFromIndex(pwf, localFile, folderObj, list);
			} else {
				file = new File(localFile);
				queue.add(new NXQueueItem("upload",
						  localBase,
						  base,
						  oi.name,
						  list,
						  file.length()), false);
			}
		}
	}

	private NXObjectIndex[] fileListToIndex(File[] files) {
		NXObjectIndex[] folderIndex = new NXObjectIndex[files.length];
		String folderFileName; char type;
		for (int y = 0; y < files.length; y++) {
			folderFileName = files[y].getName();
			File folderFile = new File(files[y].getPath());
			type = (folderFile.isDirectory()) ? 'd' : '-';
			folderIndex[y] = new NXObjectIndex(type, folderFileName);
		}
		return folderIndex;
	}

	private void localMoveFromIndex(String base, String destBase, Object[] index) {
		NXObjectIndex oi;
		String strDestFile;
		String strSrcFile;
		File file;

		NXListAdapter srcList = localPanel.getListIfShown(base);
		NXListAdapter destList = localPanel.getListIfShown(destBase);

		for (int x = 0; x < index.length; x++) {
			oi = (NXObjectIndex) index[x];
			strDestFile = destBase + '/' + oi.name;
			strSrcFile = base + '/' + oi.name;
			file = new File(strSrcFile);
			File destFile = new File(strDestFile);
			if (file.renameTo(destFile)) {
				if (destList != null) destList.addElement(oi);
			}
		}

		reloadList(srcList);
		if (destList != null) localPanel.removeList(destList.getIndex() + 1);
	}

	private void remoteMoveFromIndex(String base, String destBase, Object[] index) {
		NXObjectIndex oi;
		String strDestFile;
		String strSrcFile;

		NXListAdapter srcList = remotePanel.getListIfShown(base);
		NXListAdapter destList = remotePanel.getListIfShown(destBase);

		for (int x = 0; x < index.length; x++) {
			oi = (NXObjectIndex) index[x];
			strDestFile = destBase + '/' + oi.name;
			File destFile = new File(strDestFile);
			queue.add(new NXQueueItem("move", base, oi.name, strDestFile), true);
		}

		reloadList(srcList);
		if (destList != null) {
			reloadList(destList);
			remotePanel.removeList(destList.getIndex() + 1);
		} else {
			ftpClient.getObjectIndex().remove(destBase);
		}
	}

	private int getType(String base, NXObjectIndex oi) {
		int type;
		if (oi.type == 1) { //link
			if (oi.realType == -1) { //don't know type yet
				type = (ftpClient.isDirectory(base + "/" + oi.name)) ? 2 : 0;
				oi.setRealType(type);
			} else { //already know type*/
				type = oi.realType;
			}
		} else {
			type = oi.type;
		}
		return type;
	}

	class DeleteIndexWorker extends SwingWorker {

		private final String folder;
		private final Object[] index;
		private final NXListAdapter display;

		protected DeleteIndexWorker(String folder, Object[] index, NXListAdapter display) {
			this.folder = folder;
			this.index = index;
			this.display = display;
		}

		@Override
		protected Object doInBackground() throws Exception {
			deleteFromIndex(folder, index, true);
			reloadList(display);
			remotePanel.removeList(display.getIndex() + 1);
			return new Object();
		}

		private void deleteFromIndex(String base, Object[] index, boolean start) throws Exception {
			NXObjectIndex oi;
			String pwf;
			String[] parts = NXFormat.getNameFromPath(base);
			String name = parts[1];
			String path = parts[0];
			setStatus("Deleting \"" + name + "\".", true);
			int type;
			ftpClient.changeFolder(base);
			for (int x = 0; x < index.length; x++) {
				oi = (NXObjectIndex) index[x];
				pwf = base + '/' + oi.name;
				type = getType(base, oi);
				if (type > 0) {
					ftpClient.indexDirectory(pwf);
					NXObjectIndex folderIndex = (NXObjectIndex) ftpClient.getObjectIndex().get(pwf);
					deleteFromIndex(pwf, folderIndex.objects.values().toArray(), false);
				} else {
					queue.add(new NXQueueItem("delete", "", base, oi.name), false);
				}
			}

			if (!start) {
				queue.add(new NXQueueItem("command", "RMD", path, name), false);
			}
		}

		@Override
		public void done() {
			queue.process();
		}
	};

	class IndexWorker extends SwingWorker {

		private NXQueueItem qi;
		private final boolean download;
		private int listingSize = 0;
		private int preloadLoc = 0;
		private String folder = null;

		protected IndexWorker(NXQueueItem qi, boolean download) {
			this.qi = qi;
			this.download = download;

			String message = "";
			if (!download) {
				for (int x = 0; x < qi.preload.length; x++) {
					NXObjectIndex obj = (NXObjectIndex) qi.preload[x];
					if (obj != null) {
						String initial = (qi.remoteFolder.equals("/")) ? "" : qi.remoteFolder;
						folder = initial + '/' + obj.name;
						message = "Preloading \"" + obj.name + "\"";
						preloadLoc = x;
						break;
					}
				}
			} else {
				message = "Indexing \"" + qi.fileName + "\"";
			}

			setStatus(message, download);
		}

		@Override
		protected Object doInBackground() throws Exception {
			if (download) {
				folder = qi.remoteFolder;
			} else {
				
			}
			if (folder != null) {
				if (!ftpClient.getObjectIndex().containsKey(folder)) {
					listingSize = ftpClient.indexDirectory(folder);
				}
			}
			return new Object();
		}

		@Override
		public void done() {
			NXObjectIndex folderIndex = (NXObjectIndex) ftpClient.getObjectIndex().get(qi.remoteFolder);
			Object[] arrObjects = folderIndex.objects.values().toArray();
			queue.totalSize += listingSize;
			queue.totalTransferred += listingSize;
			if (download) {
				downloadFromIndex(qi.remoteFolder, qi.localFolder, arrObjects, null, qi.block);
			} else {
				String currDir;

				if (remotePanel.listing) {
					currDir = remotePanel.table.getDirectory();
				} else {
					currDir = remotePanel.getListBoxes(-1).getDirectory();
				}

				if (currDir.equals("")) currDir = "/";

				//check if user has moved to another directory that needs preloading
				//boolean isCurrent = qi.remoteFolder.equals(currDir);
				if (qi.preload != null && qi.preload.length > 0 /*&& isCurrent*/) {
					qi.preload[preloadLoc] = null;
					boolean cont = false;
					for (int x = 0; x < qi.preload.length; x++) {
						if (qi.preload[x] != null) {
							cont = true;
							break;
						}
					}
					if (cont) {
						preloadDirectory(qi.remoteFolder, folderIndex.objects);
					}
				}
			}
			queue.process();
		}
	};

	public class NXQueue implements ActionListener {

		public ArrayList queue = new ArrayList();
		public ArrayList errorQueue = new ArrayList();
		public ArrayList conflictQueue = new ArrayList();
		int done = 0;
		long totalTransferred = 0;
		long totalSize = 0;
		int seconds = 0;
		int currentSeconds = 0;
		String currentString = "";
		private final javax.swing.Timer timer = new javax.swing.Timer(1000, this);
		private long currentTransferred;
		boolean current;
		public boolean paused;

		public void add(NXQueueItem q, boolean priority) {
			int size = queue.size();
			int pos = (priority && size != 0) ? 1 : size;
			queue.add(pos, q);
			queuePanel.addItem(q, pos);
			totalSize += q.totalSize;
			if (size + 1 == 1) {
				doItem(queue.get(0));
				update();
				timer.start();
			}
		}

		public NXQueueItem find(String activity, String localFolder, String remoteFolder, String fileName) {
			Iterator itr = queue.iterator();
			while(itr.hasNext()) {
				 NXQueueItem element = (NXQueueItem) itr.next();
				 if (element.activity.equals(activity) && element.localFolder.equals(localFolder) && element.remoteFolder.equals(remoteFolder) && element.fileName.equals(fileName)) {
					 return element;
				 }
			}
			return null;
		}

		public void insert(NXQueueItem[] q, boolean prepend) {
			for (int x = 0; x < q.length; x++) {
				if (prepend) queue.add(x, q[x]);
				else queue.add(q[x]);
			}
		}

		public void addError(NXQueueItem q) {
			errorQueue.add(q);
		}

		public void addConflict(NXQueueItem q) {
			conflictQueue.add(q);
		}

		public Object get(int item) {
			return queue.get(item);
		}

		public int getDone() {
			return done;
		}

		public int getLength() {
			return queue.size();
		}

		public void stopCurrent() {
			/*if (queue.length == 0) return;
			NXQueueItem qi = (NXQueueItem) queue.get(0);*/
			ftpClient.stopCurrent();
		}

		public void process() {
			currentTransferred = 0;
			currentSeconds = 0;
			if (queue.size() > 0) {
				queuePanel.removeItem((NXQueueItem) queue.get(0));
				queue.remove(0);
			}
			if (!paused) continueQueue();
			else current = false;
		}

		public void continueQueue() {
			if (queue.size() > 0) {
				doItem(queue.get(0));
				done++;
			} else { //end of queue
				current = false;
				done = 0;
				seconds = 0;
				totalTransferred = 0;
				timer.stop();
				totalSize = 0;

				if (errorQueue.size() > 0) {
					NXTransferDialog m = new NXTransferDialog(frame, errorQueue);
					m.setVisible(true);
					errorQueue.clear();
				}

				queuePanel.updateActivity(null);
				update();
				overallProgressBar.setVisible(false);
				progressBar.setVisible(false);
			}
		}

		public void updateTransferred(long add) {
			currentTransferred += add;
			totalTransferred += add;
		}

		private void doItem(Object i) {
			current = true;
			NXQueueItem qi = (NXQueueItem) i;
			boolean explicit = qi.activity.contains("-explicit");

			if (!timer.isRunning()) timer.start();
			queuePanel.updateActivity(qi);

			if (qi.activity.equals("list")) {
				ListingWorker lw = new ListingWorker(qi.remoteFolder, false, qi.list);
				if (qi.fakePath != null) { //used for directory links so that real path is updated
					lw.fake = qi.fakePath;
				}
				lw.execute();
			} else if (qi.activity.contains("mkdir")) {
				qi.localFolder = "MKD";
				(new CommandWorker(qi, "Creating remote directory "
						+ qi.fileName + "...", explicit)).execute();
			} else if (qi.activity.contains("command")) {
				(new CommandWorker(qi, "", explicit)).execute();
			} else if (qi.activity.equals("upload")) {
				(new UploadWorker(qi)).execute();
			} else if (qi.activity.equals("download")) {
				(new DownloadWorker(qi)).execute();
			} else if (qi.activity.equals("preview")) {
				(new DownloadWorker(qi, true, false)).execute();
			} else if (qi.activity.equals("open")) {
				(new DownloadWorker(qi, false, true)).execute();
			} else if (qi.activity.equals("index")) {
				(new IndexWorker(qi, true)).execute();
			} else if (qi.activity.equals("preload")) {
				(new IndexWorker(qi, false)).execute();
			} else if (qi.activity.equals("delete")) {
				(new DeleteWorker(qi)).execute();
			} else if (qi.activity.equals("deletefromindex")) {
				(new DeleteIndexWorker(qi.remoteFolder, qi.values, (NXListAdapter) qi.display)).execute();
			} else if (qi.activity.equals("rename")) {
				(new RenameWorker(qi)).execute();
			} else if (qi.activity.equals("move")) {
				(new RenameWorker(qi, true)).execute();
			} else if (qi.activity.startsWith("type")) {
				(new TypeWorker(qi)).execute();
			}
		}

		private void update() {
			seconds++;
			currentSeconds++;
			
			queuePanel.updateProgress(currentTransferred, currentSeconds, totalTransferred, seconds, totalSize);
			updateOverall();
			updateSpeed();
		}

		private void updateSpeed() {
			int bps = 0;
			if (currentSeconds != 0) {
				bps = (int) (currentTransferred / currentSeconds);
				progressBar.setString(currentString + " at " + NXFormat.fileSize(bps, true) + "/s");
			}

			if (seconds != 0) {
				bps = (int) (totalTransferred / seconds);
			} else {
				bps = 0;
			}
			int eta = NXFormat.getEta(totalSize, totalTransferred, bps);
			if (seconds != 0) {
				overallProgressBar.setString("All Actions" + eta(eta));
			}
		}

		private String eta(int eta) {
			String strEta = NXFormat.parseTime(eta);
			if (!strEta.equals("")) {
				strEta = " (" + strEta + " remaining)";
			}
			return strEta;
		}

		public void actionPerformed(ActionEvent e) { //timer to update queue data
			update();
		}

		private void removeAll() {
			queue.clear();
			queuePanel.removeAllItems();
			update();
		}
	}

	private class NXDropListener implements DropTargetListener {

		private JToolTip label;
		private NXDragFeedbackLabel glassPane;
		private NXListAdapter source;
		private boolean reject;
		private String initial;
		private String dir;

		public NXDropListener(NXListAdapter source) {
			this.source = source;

			dir = (source.getDirectory().equals("")) ? "/" : NXFormat.getNameFromPath(source.getDirectory(), false);
			label = new JToolTip();
			label.setTipText(initial + dir);
			label.setOpaque(true);
			label.setSize(label.getPreferredSize());
			if (frame != null) {
				glassPane = (NXDragFeedbackLabel) frame.getGlassPane();
			} else {
				glassPane = dragFeedbackLabel;
			}
		}

		public void dragEnter(DropTargetDragEvent dtde) {
			if (source.equals(dragItem)) {
				dtde.rejectDrag();
			}
		}

		public void dragOver(DropTargetDragEvent dtde) {
			if (reject) {
				return;
			}
			Component c = label;
			BufferedImage image = new BufferedImage(c.getWidth(),
					c.getHeight(),
					BufferedImage.TYPE_INT_ARGB);
			Graphics g = image.getGraphics();
			c.paint(g);

			int top = 0;
			int listLeft = 0;
			boolean leftSide = false;
			top = (source.getLocal()) ? splitBrowser.getDividerLocation() : 0;

			NXFtpBrowserPanel panel = (!source.getLocal()) ? remotePanel : localPanel;

			if (source.getType() == false) {
				//list
				NXList l = (NXList) source;
				NXScrollPane sp = (NXScrollPane) l.getParent().getParent();
				listLeft = sp.getX();
				listLeft += splitOverall.getDividerLocation() + splitOverall.getDividerSize();
				top -= ((JViewport) l.getParent()).getViewPosition().y;
				leftSide = dtde.getLocation().x < l.getWidth() / 2;
			}

			NXListAdapter a = (NXListAdapter) source;
			a.setDropLeft(leftSide);
			Object[] obj = a.getSelectedValues();

			if (dragItem != null) {
				if (source.getLocal() != dragItem.getLocal()) {
					initial = source.getLocal() ? "Download" : "Upload";
				} else {
					initial = "Move";
				}
				initial += " to ";
			} else {
				initial = "Upload to ";
			}

			if (obj.length > 0 && leftSide) {
				NXObjectIndex oi = (NXObjectIndex) obj[0];
				label.setTipText((oi.type == 2 || oi.realType == 2) ? initial + oi.name : initial + dir);
			} else {
				label.setTipText(initial + dir);
			}

			label.setSize(label.getPreferredSize());

			glassPane.setVisible(true);

			Point p = (Point) dtde.getLocation().clone();

			int left = 0;
			JViewport scrollBrowser = (JViewport) panel.browserScroll.getViewport();
			int start = scrollBrowser.getViewPosition().x;
			int end = scrollBrowser.getViewSize().width;

			int diff = listLeft - start + p.x;

			if (diff + label.getSize().width + 24 > end) {
				left = end - label.getSize().width - 24;
			} else {
				left = diff;
			}

			p.x = left;
			p.y += top;
			glassPane.setPoint(p);
			glassPane.setImage(image);
			glassPane.repaint();
		}

		public void dropActionChanged(DropTargetDragEvent dtde) {
		}

		public void dragExit(DropTargetEvent dte) {
			glassPane.setVisible(false);
		}

		public void drop(DropTargetDropEvent dtde) {
			dtde.getLocation();
			glassPane.setVisible(false);
		}
	}

}
class NXDragFeedbackLabel extends JLabel {

	private AlphaComposite composite;
	private BufferedImage dragged = null;
	private Point location = new Point(0, 0);

	public NXDragFeedbackLabel() {
		super();
		setOpaque(false);
		composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f);
	}

	public void setImage(BufferedImage dragged) {
		this.dragged = dragged;
	}

	public void setPoint(Point location) {
		this.location = location;
	}

	public void paintComponent(Graphics g) {
		if (dragged == null) {
			return;
		}

		Graphics2D g2 = (Graphics2D) g;
		g2.setComposite(composite);
		g2.drawImage(dragged,
				(int) (location.getX() + 24),
				(int) (location.getY() + 64),
				null);
	}
}
